// Copyright (C) 2010 David Sugar, Tycho Softworks.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU uCommon C++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.

#include <ucommon/secure.h>
#include <ctype.h>
#include <sys/stat.h>

using namespace UCOMMON_NAMESPACE;

static shell::flagopt helpflag('h',"--help",    _TEXT("display this list"));
static shell::flagopt althelp('?', NULL, NULL);
static shell::flagopt erase('e', "--remove", _TEXT("Remove symlink"));
static shell::flagopt force('f', "--force", _TEXT("force replace target"));
static shell::flagopt info('i', "--info", _TEXT("show link target info"));
static shell::flagopt hard('h', "--hardlink", _TEXT("create hardlink only"));

static const char *argv0 = "ln";

static void report(const char *path, int code)
{
    const char *err = _TEXT("i/o error");

    switch(code) {
    case EACCES:
    case EPERM:
        err = _TEXT("permission denied");
        break;
    case EROFS:
        err = _TEXT("read-only file system");
        break;
    case ENODEV:
    case ENOENT:
        err = _TEXT("no such file or directory");
        break;
    case ENOTDIR:
        err = _TEXT("not a directory");
        break;
    case ENOTEMPTY:
        err = _TEXT("directory not empty");
        break;
    case ENOSPC:
        err = _TEXT("no space left on device");
        break;
    case EBADF:
    case ENAMETOOLONG:
        err = _TEXT("bad file path");
        break;
    case EBUSY:
    case EINPROGRESS:
        err = _TEXT("file or directory busy");
        break;
    case EINTR:
        err = _TEXT("operation interupted");
        break;
    case EINVAL:
        err = _TEXT("file is not a valid link");
        break;
#ifdef  ELOOP
    case ELOOP:
        err = _TEXT("too many sym links");
        break;
#endif
    }

    shell::errexit(1, "*** %s: %s: %s\n", argv0, path, err);
}

PROGRAM_MAIN(argc, argv)
{
    int err = 0;
    const char *path;
    char buffer[512];
    int errcode = 0;

    shell::bind("ln");
    shell args(argc, argv);
    argv0 = args.argv0();

    if(is(helpflag) || is(althelp)) {
err:
        printf("%s: %s %s\n", _TEXT("Usage"), argv0, _TEXT("[options] [path] target"));
        printf("%s\n\n", _TEXT("Link path with a target"));
        printf("%s\n", _TEXT("Options:"));
        shell::help();
        printf("\n%s\n", _TEXT("Report bugs to dyfet@gnu.org"));
        PROGRAM_EXIT(errcode);
    }

    if(args() < 1 || args() > 2) {
        errcode = 2;
        goto err;
    }

    if(args() == 1) {
        path = args[0];
        if(!fsys::is_link(path))
            err = EINVAL;
        else if(is(erase))
            err = fsys::unlink(path);
        else {
            if(!is(info)) {
                errcode = 2;
                goto err;
            }
            err = fsys::linkinfo(path, buffer, sizeof(buffer));
            if(!err)
                printf("%s\n", buffer);
        }
    }
    else {
        path = args[1];
        if(is(force))
            fsys::unlink(path);
        if(is(hard))
            err = fsys::hardlink(argv[0], path);
        else
            err = fsys::link(args[0], path);
    }

    if(err)
        report(path, err);

    PROGRAM_EXIT(0);
}

